<?php

namespace wx;

// 微信 服务
class WeixinService
{
    private $appid;
    private $appsecret;
    private $access_token;

    private $mchid;
    private $paykey;
    private $pay_notifyurl;

    public function __construct()
    {
        $this->appid = config('wx_appid');
        $this->appsecret = config('wx_secretkey');
        $this->mchid = config('wx_mchid');
        $this->paykey = config('paykey');
        $this->pay_notifyurl = config('pay_notifyurl');
        $this->access_token = $this->getAccessToken();
    }

    private function getAccessToken()
    {
        $accesstoken = cache('wx_access_token');
        if ($accesstoken) {
            return $accesstoken;
        } else {
            $service_url = sprintf('https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s', $this->appid, $this->appsecret);
            $result = HttpGet($service_url);
            $result = json_decode($result);
            if (isset($result->access_token) && strlen($result->access_token) > 10) {
                cache('wx_access_token', $result->access_token, 3600);

                return $result->access_token;
            }
        }
    }

    // 取二维码 10w张限制
    public function getQrCode($path, $width = 430)
    {
        //10W个二维码 以后改这个url
        //https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=ACCESS_TOKEN
        $service_url = sprintf('https://api.weixin.qq.com/wxa/getwxacode?access_token=%s', $this->access_token);
        $para = array('path' => $path, 'width' => $width, 'is_hyaline' => true);
        $result = post($service_url, $para);

        return $result;
    }

    // 无限制取二维码
    public function getQrCodeUnlimit($page, $width = 430, $scene = '', $is_hyaline = false)
    {
        $service_url = sprintf('https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=%s', $this->access_token);
        $para = array('page' => $page, 'width' => $width, 'scene' => $scene, 'is_hyaline' => $is_hyaline);
        $result = post($service_url, $para);

        return $result;
    }

    // 2次重发
    public function repost($res, $func, $para)
    {
        $c = intval(cache('wx_access_token_get'));
        if (40001 == $res['errcode'] && $c < 2) {
            cache('wx_access_token_get', $c + 1);
            cache('wx_access_token', null);
            $this->access_token = $this->getAccessToken();

            return $this->$func($para);
        } else {
            return $res;
        }
    }

    // 小程序发送模板消息
    public function sendTemplateMsg($touser, $form_id, $template_id, $page, $data, $time2wait = 0)
    {
        if ($time2wait==0) {
            $h = date("H");
            if ($h>22) {
                $time2wait = strtotime(date('Y-m-d 08:30:00', strtotime("+1 day"))) - time();
            }
            if ($h<8) {
                $time2wait = strtotime(date('Y-m-d 08:30:00')) - time();
            }
        }
        $service_url = 'https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token=%s';
        $para = array('touser' => $touser, 'form_id' => $form_id, 'template_id' => $template_id, 'page' => $page, 'data' => $data);
        $consumer = \app\sys\job\postQueue::class;
        $payload = ['ct' => time(), 'type' => '发送小程序模板消息', 'service_url' => $service_url, 'para' => $para];
        $queue = 'postQueue';
        $pushed = \think\Queue::later($time2wait, $consumer, $payload, $queue);
    }

    // 小程序发送客服消息(文本)
    public function sendCustomerMsg($touser, $content)
    {
        $service_url = 'https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=%s';
        $para = array('touser' => $touser, 'msgtype' => 'text', 'text' => ['content' => $content]);
        $consumer = \app\sys\job\postQueue::class;
        $payload = ['ct' => time(), 'type' => '发送客服消息', 'service_url' => $service_url, 'para' => $para];
        $queue = 'postQueue';
        $pushed = \think\Queue::push($consumer, $payload, $queue);
    }

    /**
     * 	作用：格式化参数，签名过程需要使用.
     */
    public function formatBizQueryParaMap($paraMap, $urlencode)
    {
        $buff = '';
        ksort($paraMap);
        foreach ($paraMap as $k => $v) {
            if ($urlencode) {
                $v = urlencode($v);
            }
            //$buff .= strtolower($k) . "=" . $v . "&";
            $buff .= $k.'='.$v.'&';
        }
        $reqPar;
        if (strlen($buff) > 0) {
            $reqPar = substr($buff, 0, strlen($buff) - 1);
        }

        return $reqPar;
    }

    /**
     * 	作用：生成支付签名.
     */
    public function getPaySign($Obj, $payKey = null)
    {
        if (!$payKey) {
            $payKey = $this->paykey;
        }
        foreach ($Obj as $k => $v) {
            $Parameters[$k] = $v;
        }
        //签名步骤一：按字典序排序参数
        ksort($Parameters);
        $String = $this->formatBizQueryParaMap($Parameters, false);
        //echo '【string1】'.$String.'</br>';
        //签名步骤二：在string后加入KEY
        $String = $String.'&key='.$payKey;
        //echo "【string2】".$String."</br>";
        //签名步骤三：MD5加密
        $String = md5($String);
        //echo "【string3】 ".$String."</br>";
        //签名步骤四：所有字符转为大写
        $result_ = strtoupper($String);
        //echo "【result】 ".$result_."</br>";
        return $result_;
    }

    /*
        通用签名算法
    */
    public function getNormalSign($obj)
    {
        ksort($obj);
        $String = $this->formatBizQueryParaMap($obj, false);
        //echo '【string1】'.$String.'</br>';
        //签名步骤二：在string后加入KEY
        //$String = $String."&key=".$this->paykey;
        //echo "【string2】".$String."</br>";
        //签名步骤三：MD5加密
        $String = md5($String);
        //echo "【string3】 ".$String."</br>";
        //签名步骤四：所有字符转为大写
        $result_ = strtoupper($String);
        //echo "【result】 ".$result_."</br>";
        return $result_;
    }

    //获取微信预支付
    public function prePay($row)
    {
        $row['subject'] = subString($row['subject'], 30);
        $row['recharge'] = intval($row['recharge'] * 100);

        $wxPayOrder = $this->getXCXPayOrder($row);
        if ('SUCCESS' != $wxPayOrder->result_code) {
            //print_r($wxPayOrder);exit;
            $exErrorInfo = json_encode($wxPayOrder);
            $jsonError = json_decode($exErrorInfo);

            return error('支付出错！'.$jsonError->return_msg, 20000, ['error' => $jsonError->return_msg, 'detail_error' => $exErrorInfo]);
        } else {
            $prepay_id = strval($wxPayOrder->prepay_id);
            $nonceStr = $this->createNonceStr(32);
            $signType = 'MD5';
            $st = time();
            $jsPayRow = array('timeStamp' => strval($st), 'nonceStr' => $nonceStr, 'appId' => config('wx_appid'), 'package' => 'prepay_id='.$prepay_id, 'signType' => $signType);
            $jsPaySign = $this->getPaySign($jsPayRow);
            $jsPayRow['paySign'] = $jsPaySign;
            $jsPayRow['order_id'] = $row['id'];
            $jsPayRow['recharge'] = round($row['recharge'] / 100, 2);

            \think\Db::name('order')->where('id', $row['id'])->update(['prepay_id' => $prepay_id]);

            return success($jsPayRow);
        }
    }

    //获取小程序的支付订单号
    public function getXCXPayOrder($row, $ttype = 'JSAPI', $notifyurl = false)
    {
        $payPara = array();
        $payPara['appid'] = $this->appid;
        $payPara['mch_id'] = $this->mchid;
        $payPara['device_info'] = 'XCX';
        $payPara['nonce_str'] = $this->createNonceStr(32);
        $payPara['body'] = subString($row['subject'], 25);
        $payPara['out_trade_no'] = strval($row['id']);
        $payPara['total_fee'] = $row['recharge'];
        $payPara['spbill_create_ip'] = getRemoteIp();
        $payPara['notify_url'] = ($notifyurl ? $notifyurl : $this->pay_notifyurl);
        $payPara['trade_type'] = $ttype;
        $payPara['openid'] = $row['openid'];
        $payPara['sign'] = $this->getPaySign($payPara);
        //print_r($payPara);exit;
        $xmlPara = $this->arrayToXml($payPara);
        $service_url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
        $result = httpPost($service_url, $xmlPara);
        libxml_disable_entity_loader(true);
        $result = simplexml_load_string($result, 'SimpleXMLElement', LIBXML_NOCDATA);

        return $result;
    }

    /**
     * 微信支付后的订单退款.
     *
     * @param string $transaction_id 微信支付流水号
     * @param string $out_refund_no  商户退款订单号
     * @param string $total_fee      订单总额
     * @param string $refund_fee     退款金额
     *
     * @return xml
     */
    public function payOrderRefund($transaction_id, $order_id, $total_fee, $refund_fee)
    {
        $service_url = 'https://api.mch.weixin.qq.com/secapi/pay/refund';
        $payPara = array();
        $payPara['appid'] = $this->appid;
        $payPara['mch_id'] = $this->mchid;
        $payPara['nonce_str'] = $this->createNonceStr(32);
        $payPara['transaction_id'] = $transaction_id;
        $payPara['out_refund_no'] = $order_id;
        $payPara['total_fee'] = intval($total_fee * 100);
        $payPara['refund_fee'] = intval($refund_fee * 100);
        $payPara['op_user_id'] = $this->mchid; //操作员
        $payPara['sign'] = $this->getPaySign($payPara);

        $xmlPara = $this->arrayToXml($payPara);

        $result = $this->wxPayPost($service_url, $xmlPara);

        libxml_disable_entity_loader(true);
        $result = simplexml_load_string($result, 'SimpleXMLElement', LIBXML_NOCDATA);

        $jsonStr = json_encode($result);
        $jsonArray = json_decode($jsonStr, true);

        return $jsonArray;
    }

    //服务端自动支付(微信账号)
    public function serverAutoPay($row)
    {
        $service_url = 'https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers';
        $payPara = array();
        $payPara['mch_appid'] = $this->appid;
        $payPara['mchid'] = $this->mchid;
        $payPara['nonce_str'] = $this->createNonceStr(32);
        $payPara['partner_trade_no'] = strval($row['id']);
        $payPara['openid'] = $row['openid'];
        $payPara['check_name'] = 'NO_CHECK';
        $payPara['amount'] = round($row['pay_money'] * 100);
        $payPara['desc'] = $row['desc'];
        $payPara['spbill_create_ip'] = getRemoteIp();
        $payPara['sign'] = $this->getPaySign($payPara);
        $xmlPara = $this->arrayToXml($payPara);
        $result = $this->wxPayPost($service_url, $xmlPara);
        libxml_disable_entity_loader(true);
        $result = simplexml_load_string($result, 'SimpleXMLElement', LIBXML_NOCDATA);

        $jsonStr = json_encode($result);
        $jsonArray = json_decode($jsonStr, true);

        return $jsonArray;
    }

    private function wxPayPost($url, $vars, $second = 30, $aHeader = array())
    {
        $ch = curl_init();
        //超时时间
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        //这里设置代理，如果有的话
        //curl_setopt($ch,CURLOPT_PROXY, '10.206.30.98');
        //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_SSLCERT, __DIR__.'/apiclient_cert.pem');
        curl_setopt($ch, CURLOPT_SSLKEY, __DIR__.'/apiclient_key.pem');

        if (count($aHeader) >= 1) {
            curl_setopt($ch, CURLOPT_HTTPHEADER, $aHeader);
        }

        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $vars);
        $data = curl_exec($ch);
        if ($data) {
            curl_close($ch);

            return $data;
        } else {
            $error = curl_errno($ch);
            echo "call faild, errorCode:$error\n";
            curl_close($ch);

            return false;
        }
    }

    /**
     * 	作用：array转xml.
     */
    private function arrayToXml($arr)
    {
        $xml = '<xml>';
        foreach ($arr as $key => $val) {
            if (is_numeric($val)) {
                $xml .= '<'.$key.'>'.$val.'</'.$key.'>';
            } else {
                $xml .= '<'.$key.'><![CDATA['.$val.']]></'.$key.'>';
            }
        }
        $xml .= '</xml>';

        return $xml;
    }

    public function xmlToArray($xml)
    {
        //将XML转为array
        libxml_disable_entity_loader(true);
        $array_data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);

        return $array_data;
    }

    //解密小程序的返回信息
    public static $OK = 0;
    public static $IllegalAesKey = -41001;
    public static $IllegalIv = -41002;
    public static $IllegalBuffer = -41003;
    public static $DecodeBase64Error = -41004;

    public function decryptData($sessionKey, $encryptedData, $iv, &$data)
    {
        if (24 != strlen($sessionKey)) {
            return self::$IllegalAesKey;
        }
        $aesKey = base64_decode($sessionKey);

        if (24 != strlen($iv)) {
            return self::$IllegalIv;
        }
        $aesIV = base64_decode($iv);

        $aesCipher = base64_decode($encryptedData);
        //echo $aesIV,
        $result = $this->prpcryptDecrypt($aesKey, $aesCipher, $aesIV);
        //print_r($result);exit;
        if (0 != $result[0]) {
            return $result[0];
        }

        $dataObj = json_decode($result[1], true);
        if (null == $dataObj) {
            return self::$IllegalBuffer;
        }
        if ($dataObj['watermark']['appid'] != $this->appid) {
            return self::$IllegalBuffer;
        }
        $data = json_decode($result[1], true);

        return self::$OK;
    }

    public function createNonceStr($length = 16)
    {
        $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
        $str = '';
        for ($i = 0; $i < $length; ++$i) {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }

        return $str;
    }

    private function prpcryptDecrypt($key, $aesCipher, $aesIV)
    {
        try {
            $decrypted = openssl_decrypt($aesCipher, 'AES-128-CBC', $key, 1, $aesIV);
            // $module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');

            // mcrypt_generic_init($module, $key, $aesIV);

            // //解密
            // $decrypted = mdecrypt_generic($module, $aesCipher);
            // mcrypt_generic_deinit($module);
            // mcrypt_module_close($module);
        } catch (Exception $e) {
            return array(self::$IllegalBuffer, null);
        }

        try {
            //去除补位字符
            $result = $this->pKCS7Decode($decrypted);
        } catch (Exception $e) {
            //print $e;
            return array(self::$IllegalBuffer, null);
        }

        return array(0, $result);
    }

    private static $block_size = 16;

    private function pKCS7Encode($text)
    {
        $block_size = self::$block_size;
        $text_length = strlen($text);
        //计算需要填充的位数
        $amount_to_pad = self::$block_size - ($text_length % self::$block_size);
        if (0 == $amount_to_pad) {
            $amount_to_pad = self::block_size;
        }
        //获得补位所用的字符
        $pad_chr = chr($amount_to_pad);
        $tmp = '';
        for ($index = 0; $index < $amount_to_pad; ++$index) {
            $tmp .= $pad_chr;
        }

        return $text.$tmp;
    }

    private function pKCS7Decode($text)
    {
        $pad = ord(substr($text, -1));
        if ($pad < 1 || $pad > 32) {
            $pad = 0;
        }

        return substr($text, 0, (strlen($text) - $pad));
    }
}
